#include "log.h"
#include "util.h"
#include "mm.h"

typedef struct log_rotator_t
{
	/* rotate type */
	int type;

	union
	{
		/* for ELogRotator_Size */
		size_t max_file_size;

		/* for ELogRotator_Hour & ELogRotator_Date */
		time_t log_start_time;
	};

	/* current log file */
	int fd;
	char log_file[MAX_LOG_NAME_LEN];
	uint32_t log_suffix;
	size_t log_file_size;
	
	/* rotate function */
	int (*check_and_rotate)(struct log_t*);
}log_rotator_t;

typedef struct log_t
{
	struct log_rotator_t* rotator;

	int log_level;
	
	char log_name[MAX_LOG_NAME_LEN];
}log_t;

int log_do_date_rotate(struct log_t* log)
{
	assert(log);
	assert(log->rotator->type == ELogRotator_Date);

	/* need to do rotate */
	time_t now = time(NULL);
	if(now >= log->rotator->log_start_time + 60 * 60 * 24)
	{
		log->rotator->log_suffix = get_date_number(now);
		sprintf(log->rotator->log_file, "%s.%u", log->log_name, log->rotator->log_suffix);
		log->rotator->fd = open(log->rotator->log_file, O_CREAT | O_RDWR | O_APPEND, 0666);
		if(log->rotator->fd < 0)
		{
			fprintf(stderr, "open log file[%s] fail\n", log->rotator->log_file);
			return -1;
		}
	}	
	return 0;
}

int log_do_hour_rotate(struct log_t* log)
{
	assert(log);
	assert(log->rotator->type == ELogRotator_Hour);

	/* need to do rotate */
	time_t now = time(NULL);
	if(now >= log->rotator->log_start_time + 60 * 60)
	{
		log->rotator->log_suffix = get_hour_number(now);
		sprintf(log->rotator->log_file, "%s.%u", log->log_name, log->rotator->log_suffix);
		log->rotator->fd = open(log->rotator->log_file, O_CREAT | O_RDWR | O_APPEND, 0666);
		if(log->rotator->fd < 0)
		{
			fprintf(stderr, "open log file[%s] fail\n", log->rotator->log_file);
			return -1;
		}
	}	
	return 0;
}

int log_do_size_rotate(struct log_t* log)
{
	assert(log);
	assert(log->rotator->type == ELogRotator_Size);

	/* need to do rotate */
	if(log->rotator->log_file_size >= log->rotator->max_file_size)
	{
		log->rotator->log_suffix ++;
		if(log->rotator->log_suffix > MAX_LOG_SIZE_ROTATE_COUNT)
			log->rotator->log_suffix = 1;
		log->rotator->log_file_size = 0;
		sprintf(log->rotator->log_file, "%s.%u", log->log_name, log->rotator->log_suffix);
		log->rotator->fd = open(log->rotator->log_file, O_CREAT | O_RDWR | O_TRUNC, 0666);
		if(log->rotator->fd < 0)
		{
			fprintf(stderr, "open log file[%s] fail\n", log->rotator->log_file);
			return -1;
		}
	}
	
	return 0;
}

int log_date_rotator_init(struct log_t* log)
{
	assert(log && log->rotator);

	/* log rotator type */
	log->rotator->type = ELogRotator_Date;
	log->rotator->check_and_rotate = log_do_date_rotate;

	/* log start time */
	time_t now = time(NULL);
	struct tm now_tm;
	localtime_r(&now, &now_tm);	
	now_tm.tm_hour = 0;
	now_tm.tm_min  = 0;
	now_tm.tm_sec  = 0;
	log->rotator->log_start_time = mktime(&now_tm);

	/* log file name */
	log->rotator->log_suffix = get_date_number(now);
	sprintf(log->rotator->log_file, "%s.%u", log->log_name, log->rotator->log_suffix);

	/* open log file */
	 if (0 == access(log->rotator->log_file, F_OK | R_OK | W_OK))
	{
		log->rotator->fd = open(log->rotator->log_file, O_RDWR | O_APPEND, 0666);
	}
	else
	{
		log->rotator->fd = open(log->rotator->log_file, O_CREAT | O_RDWR | O_APPEND, 0666);
	}
	if(log->rotator->fd < 0)
	{
		fprintf(stderr, "open log file[%s] fail\n", log->rotator->log_file);
		return -1;
	}
	return 0;
}

int log_hour_rotator_init(struct log_t* log)
{
	assert(log && log->rotator);

	/* log rotator type */
	log->rotator->type = ELogRotator_Hour;
	log->rotator->check_and_rotate = log_do_hour_rotate;

	/* log start time */
	time_t now = time(NULL);
	struct tm now_tm;
	localtime_r(&now, &now_tm);	
	now_tm.tm_min  = 0;
	now_tm.tm_sec  = 0;
	log->rotator->log_start_time = mktime(&now_tm);

	/* log file name */
	log->rotator->log_suffix = get_hour_number(now);
	sprintf(log->rotator->log_file, "%s.%u", log->log_name, log->rotator->log_suffix);

	/* open log file */
	 if (0 == access(log->rotator->log_file, F_OK | R_OK | W_OK))
	{
		log->rotator->fd = open(log->rotator->log_file, O_RDWR | O_APPEND, 0666);
	}
	else
	{
		log->rotator->fd = open(log->rotator->log_file, O_CREAT | O_RDWR | O_APPEND, 0666);
	}
	if(log->rotator->fd < 0)
	{
		fprintf(stderr, "open log file[%s] fail\n", log->rotator->log_file);
		return -1;
	}
	return 0;
}

int log_size_rotator_init(struct log_t* log, int max_size)
{
	assert(log && log->rotator);
	
	log->rotator->type = ELogRotator_Size;
	log->rotator->max_file_size = max_size;
	log->rotator->check_and_rotate = log_do_size_rotate;
	log->rotator->log_file_size = 0;
	size_t log_name_len = strnlen(log->log_name, MAX_LOG_NAME_LEN);

	/* find log directory */
	char* dir_name = dirname(log->log_name);
	DIR* dir = opendir(dir_name);
	if(dir == NULL)
	{
		fprintf(stderr, "log directory:[%s] can't open fail\n", dir_name);
		return -1;
	}

	/* find current log file and its suffix */
	struct dirent* dirp;
	uint32_t max_suffix = 0;
	while((dirp = readdir(dir)) != NULL)
	{
		if(0 == strncmp(dirp->d_name, log->log_name, log_name_len))
		{
			size_t file_name_len = strlen(dirp->d_name);
			
			/* has suffix */
			if(file_name_len > log_name_len + 1)
			{
				char* suffix_str = dirp->d_name + log_name_len + 1;
				uint32_t suffix = (uint32_t)atoll(suffix_str);
				if(max_suffix < suffix)
				{
					log->rotator->log_suffix = suffix;
					sprintf(log->rotator->log_file, "%s", dirp->d_name);
					struct stat stat_data;
					stat(log->rotator->log_file, &stat_data);
					log->rotator->log_file_size = stat_data.st_size;
				}
			}
		}
	}

	/* no existed log file, create */
	if(0 == max_suffix)
	{
		log->rotator->log_suffix = 1;
		sprintf(log->rotator->log_file, "%s.%d", log->log_name, log->rotator->log_suffix);
	}

	/* open log file */
	 if (0 == access(log->rotator->log_file, F_OK | R_OK | W_OK))
	{
		log->rotator->fd = open(log->rotator->log_file, O_RDWR | O_APPEND, 0666);
	}
	else
	{
		log->rotator->fd = open(log->rotator->log_file, O_CREAT | O_RDWR | O_APPEND, 0666);
	}
	if(log->rotator->fd < 0)
	{
		fprintf(stderr, "open log file[%s] fail\n", log->rotator->log_file);
		return -1;
	}
	return 0;
}

/*
*	log_rotator_type: ELogRotator_*
*	log_name: log file name
*	args: when ELogRotator_Size, max file size
*/
struct log_t* log_init(int log_rotator_type, int log_level, const char* log_name, int args)
{
	if(!log_name) return NULL;
	struct log_t* log = (struct log_t*)MALLOC(sizeof(struct log_t));
	log->log_level = log_level;
	snprintf(log->log_name, MAX_LOG_NAME_LEN, "%s", log_name);
	log->rotator = (struct log_rotator_t*)MALLOC(sizeof(struct log_rotator_t));
	
	/* rotate by size */
	if(log_rotator_type == ELogRotator_Size)
	{
		assert(args > 0);
		if(log_size_rotator_init(log, args) < 0)
		{
			goto FAIL;
		}
	}
	/* rotate by hour */
	else if(log_rotator_type == ELogRotator_Hour)
	{
		if(log_hour_rotator_init(log) < 0)
		{
			goto FAIL;
		}
	}
	/* rotate by date */
	else if(log_rotator_type == ELogRotator_Date)
	{
		if(log_date_rotator_init(log) < 0)
		{
			goto FAIL;
		}
	}
	/* invalid type */
	else
	{
		goto FAIL;
	}

	return log;

FAIL:
	FREE(log->rotator);
	FREE(log);
	return NULL;
}

int log_release(struct log_t* log)
{
	if(log)
	{
		if(log->rotator)
			FREE(log->rotator);
		FREE(log);
	}
	return 0;
}

int log_write(struct log_t* log, 
		int level,
		struct timeval* now, 
		const char* file_name,
		int line_number,
		const char* function_name,
		char* fmt, ...)
{
	if(level <= log->log_level) 
	{ 
		char buffer[MAX_LOG_BUFFER_LEN];

		/* timestamp & file line function tag */
		get_timestamp(now, buffer);
		snprintf(buffer + strlen(buffer), MAX_LOG_BUFFER_LEN - 1 - strlen(buffer),
			"[%s:%d:%s] \t", file_name, line_number, function_name); 

		/* log content */
		va_list ap; 
		va_start(ap,fmt); 
		vsnprintf(buffer + strlen(buffer), MAX_LOG_BUFFER_LEN - 1 - strlen(buffer), fmt, ap); 
		va_end(ap); 
		strcat(buffer, "\n"); 

		/* add log file size */
		size_t content_len = strnlen(buffer, MAX_LOG_BUFFER_LEN);
		if(log->rotator->type == ELogRotator_Size)
			log->rotator->log_file_size += content_len;

		/* write log */
		if(write(log->rotator->fd, buffer, content_len)==-1)	
		{
			fprintf(stderr, "write log[%s] fail. %s\n", buffer, strerror(errno));	
			return -1;		
		}

		/* rotate check */
		log->rotator->check_and_rotate(log);
	}

	return 0;
}

int log_set_level(struct log_t* log, int log_level)
{
	if(log)
	{
		log->log_level = log_level;
		return 0;
	}
	fprintf(stderr, "set loglevel, log invalid fail.\n");	
	return -1;
}


